package archive

import (
	"context"
	"sort"

	"gitlab.com/gitlab-org/gitaly/v14/internal/gitaly/archive"
	"gitlab.com/gitlab-org/gitaly/v14/proto/go/gitalypb"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// ListArchiveOperations implements the ListArchiveOperations RPC method
func (s *server) ListArchiveOperations(ctx context.Context, req *gitalypb.ListArchiveOperationsRequest) (*gitalypb.ListArchiveOperationsResponse, error) {
	if req.GetRepository() == nil {
		return nil, status.Error(codes.InvalidArgument, "repository is required")
	}

	// Get repository path
	repoPath, err := s.locator.GetRepoPath(req.GetRepository())
	if err != nil {
		return nil, status.Errorf(codes.Internal, "failed to get repository path: %v", err)
	}

	// Determine ref types to search
	refTypes := []string{"heads", "tags", "keep-around", "merge-requests"}
	if req.GetRefType() != "" {
		refTypes = []string{req.GetRefType()}
	}

	var allOperations []*gitalypb.ArchiveOperation

	// Collect operations from all ref types and statuses
	for _, refType := range refTypes {
		operations, err := s.getOperationsForRefType(repoPath, refType, req)
		if err != nil {
			continue // Skip errors and continue with other ref types
		}
		allOperations = append(allOperations, operations...)
	}

	// Sort operations by creation time (most recent first)
	sort.Slice(allOperations, func(i, j int) bool {
		timeI := archive.ProtoTimestampToTime(allOperations[i].CreateAt)
		timeJ := archive.ProtoTimestampToTime(allOperations[j].CreateAt)
		return timeI.After(timeJ)
	})

	// Apply pagination
	total := int32(len(allOperations))
	page := req.GetPage()
	if page <= 0 {
		page = 1
	}
	pageSize := req.GetPageSize()
	if pageSize <= 0 {
		pageSize = 10
	}

	start := (page - 1) * pageSize
	end := start + pageSize

	var paginatedOperations []*gitalypb.ArchiveOperation
	if start < total {
		if end > total {
			end = total
		}
		paginatedOperations = allOperations[start:end]
	}

	return &gitalypb.ListArchiveOperationsResponse{
		Total:    total,
		Page:     page,
		PageSize: pageSize,
		Data:     paginatedOperations,
	}, nil
}

// getOperationsForRefType collects operations for a specific ref type from all statuses
func (s *server) getOperationsForRefType(repoPath, refType string, req *gitalypb.ListArchiveOperationsRequest) ([]*gitalypb.ArchiveOperation, error) {
	var operations []*gitalypb.ArchiveOperation

	// Get operations from both waiting and finished statuses
	statuses := []archive.OperationStatus{
		archive.StatusWaiting,
		archive.StatusFinished,
	}

	for _, status := range statuses {
		entries, err := s.storageManager.ReadMetadata(repoPath, refType, status)
		if err != nil {
			continue // Skip errors and continue with other statuses
		}

		for _, entry := range entries {
			// Apply filters
			if !s.matchesOperationFilters(entry, req) {
				continue
			}

			operations = append(operations, entry.ToProtoOperation())
		}
	}

	return operations, nil
}

// matchesOperationFilters checks if an entry matches the operation filters
func (s *server) matchesOperationFilters(entry archive.MetadataEntry, req *gitalypb.ListArchiveOperationsRequest) bool {
	// Check action filter
	if req.GetAction() != gitalypb.ArchiveOperation_Action(0) {
		reqAction := s.protoActionToArchiveAction(req.GetAction())
		if entry.Action != reqAction {
			return false
		}
	}

	// Use the existing matchesFilters function for other filters
	return s.matchesFilters(
		entry,
		req.GetCreatorIds(),
		req.GetCreatorNames(),
		req.GetReason(),
		req.GetStartTime(),
		req.GetEndTime(),
	)
}

// protoActionToArchiveAction converts proto action to archive action
func (s *server) protoActionToArchiveAction(action gitalypb.ArchiveOperation_Action) archive.OperationAction {
	switch action {
	case gitalypb.ArchiveOperation_ARCHIVE:
		return archive.ActionArchive
	case gitalypb.ArchiveOperation_RESTORE:
		return archive.ActionRestore
	case gitalypb.ArchiveOperation_DELETE:
		return archive.ActionDelete
	default:
		return ""
	}
}

// protoStatusToInternalStatus converts gRPC status to internal status
func protoStatusToInternalStatus(protoStatus gitalypb.ArchiveOperation_Status) archive.OperationStatus {
	switch protoStatus {
	case gitalypb.ArchiveOperation_WAITING:
		return archive.StatusWaiting
	case gitalypb.ArchiveOperation_DONE:
		return archive.StatusFinished
	default:
		return archive.StatusWaiting // Default to waiting for unknown status
	}
}
